package com.niodata.dp.util;

import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;
import org.joda.time.Days;
import org.joda.time.Duration;
import org.joda.time.DurationFieldType;
import org.joda.time.Hours;
import org.joda.time.Minutes;
import org.joda.time.Months;
import org.joda.time.Period;
import org.joda.time.Seconds;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

public class TimeUtil {

  public static final DateTimeFormatter defaultFormatter = DateTimeFormatter
        .ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(
      ZoneId.systemDefault());

  public static Date durationsLater(int duration, TimeUnit unit) {
    Date date = new Date();
    DateTime dateTime = new DateTime(date);
    DurationFieldType type = null;
    if (unit.equals(TimeUnit.HOURS)) {
      type = DurationFieldType.hours();
    } else if (unit.equals(TimeUnit.MINUTES)) {
      type = DurationFieldType.minutes();
    } else if (unit.equals(TimeUnit.SECONDS)) {
      type = DurationFieldType.seconds();
    } else if (unit.equals(TimeUnit.DAYS)) {
      type = DurationFieldType.days();
    }
    dateTime = dateTime.withFieldAdded(type, duration);
    return dateTime.toDate();
  }

  public static Date addYears(Date date, int years) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.years(), years);
    return dateTime.toDate();
  }

  public static Date addMonths(Date date, int months) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.months(), months);
    return dateTime.toDate();
  }

  public static Date addDays(Date date, int days) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.days(), days);
    return dateTime.toDate();
  }

  public static Date addHours(Date date, int hours) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.hours(), hours);
    return dateTime.toDate();
  }

  public static Date addMinutes(Date date, int minutes) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.minutes(), minutes);
    return dateTime.toDate();
  }

  public static Date addSeconds(Date date, int seconds) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.seconds(), seconds);
    return dateTime.toDate();
  }

  public static Date addHalfDay(Date date) {
    DateTime dateTime = new DateTime(date);
    dateTime = dateTime.withFieldAdded(DurationFieldType.halfdays(), 1);
    return dateTime.toDate();
  }

  public static int monthsBetween(Date d1, Date d2) {
    DateTime dateTime1 = new DateTime(d1);
    DateTime dateTime2 = new DateTime(d2);
    return Months.monthsBetween(dateTime2, dateTime1).getMonths();
  }

  public static int daysBetween(Date d1, Date d2) {
    DateTime dateTime1 = new DateTime(d1);
    DateTime dateTime2 = new DateTime(d2);
    return Days.daysBetween(dateTime2, dateTime1).getDays();
  }

  public static int hoursBetween(Date d1, Date d2) {
    DateTime dateTime1 = new DateTime(d1);
    DateTime dateTime2 = new DateTime(d2);
    return Hours.hoursBetween(dateTime2, dateTime1).getHours();
  }

  public static int minutesBetween(Date d1, Date d2) {
    DateTime dateTime1 = new DateTime(d1);
    DateTime dateTime2 = new DateTime(d2);
    return Minutes.minutesBetween(dateTime2, dateTime1).getMinutes();
  }

  public static int secondsBetween(Date d1, Date d2) {
    DateTime dateTime1 = new DateTime(d1);
    DateTime dateTime2 = new DateTime(d2);
    return Seconds.secondsBetween(dateTime2, dateTime1).getSeconds();
  }

  public static Duration duration(Date d1, Date d2) {
    DateTime dateTime1 = new DateTime(d1);
    DateTime dateTime2 = new DateTime(d2);
    Duration duration = new Duration(dateTime1, dateTime2);
    return duration;
  }

  public static Duration durationOfSeconds(int seconds) {
    return new Duration(seconds * 1000);
  }

  public static Duration durationOfMinutes(int minutes) {
    return new Duration(minutes * 60 * 1000);
  }

  public static Duration durationOfHours(int hours) {
    return new Duration(hours * 60 * 60 * 1000);
  }

  public static Duration durationOfDays(int days) {
    return new Duration(days * 24 * 60 * 60 * 1000);
  }

  public static Duration durationOf(int days, int hours, int minutes, int seconds) {
    return new Duration(seconds * 1000)
      .plus(new Duration(minutes * 60 * 1000))
      .plus(new Duration(hours * 60 * 60 * 1000))
      .plus(new Duration(days * 24 * 60 * 60 * 1000));
  }

  public static String toReadableDuration(Duration duration) {
    Period period = duration.toPeriod();
    PeriodFormatterBuilder builder = new PeriodFormatterBuilder().printZeroAlways();
    if (period.getHours() > 0) {
      builder.appendHours().appendSeparator("小时 ");
    }
    if (period.getMinutes() > 0) {
      builder.appendMinutes().appendSeparator("分 ");
    }
    builder.appendSeconds().appendSuffix("秒");
    PeriodFormatter periodFormatter = builder.toFormatter();
    return periodFormatter.print(period);
  }

  public static String toReadableDuration(Date d1, Date d2) {
    Duration duration = duration(d1, d2);
    return toReadableDuration(duration);
  }

  public static String format(Date date, String format) {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    return sdf.format(date);
  }

  public static String format(Date date, String format, TimeZone timeZone) {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    sdf.setTimeZone(timeZone);
    return sdf.format(date);
  }

  public static Date parse(String timeStr, String format) throws Exception {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    return sdf.parse(timeStr);
  }

  public static Date parse(String timeStr, String format, TimeZone zone) throws Exception {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    sdf.setTimeZone(zone);
    return sdf.parse(timeStr);
  }

  public static String formatDefault(Date date) {
    String dateStr = null;
    if (date != null) {
      dateStr = defaultFormatter.format(date.toInstant());
    }
    return dateStr;
  }


  public static Date roundToSecond(Date origin) {
    DateTime dt = new DateTime(origin);
    dt = dt.withField(DateTimeFieldType.millisOfSecond(), 0);
    return dt.toDate();
  }

  public static Date roundToMinute(Date origin) {
    DateTime dt = new DateTime(origin);
    dt = dt.withField(DateTimeFieldType.millisOfSecond(), 0);
    dt = dt.withField(DateTimeFieldType.secondOfMinute(), 0);
    return dt.toDate();
  }

  public static Date roundToHour(Date origin) {
    DateTime dt = new DateTime(origin);
    dt = dt.withField(DateTimeFieldType.millisOfSecond(), 0);
    dt = dt.withField(DateTimeFieldType.secondOfMinute(), 0);
    dt = dt.withField(DateTimeFieldType.minuteOfHour(), 0);
    return dt.toDate();
  }

  public static Date roundToDay(Date origin) {
    DateTime dt = new DateTime(origin);
    dt = dt.withField(DateTimeFieldType.millisOfSecond(), 0);
    dt = dt.withField(DateTimeFieldType.secondOfMinute(), 0);
    dt = dt.withField(DateTimeFieldType.minuteOfHour(), 0);
    dt = dt.withField(DateTimeFieldType.hourOfDay(), 0);
    return dt.toDate();
  }

  public static Date roundToMonth(Date origin) {
    DateTime dt = new DateTime(origin);
    dt = dt.withField(DateTimeFieldType.millisOfSecond(), 0);
    dt = dt.withField(DateTimeFieldType.secondOfMinute(), 0);
    dt = dt.withField(DateTimeFieldType.minuteOfHour(), 0);
    dt = dt.withField(DateTimeFieldType.hourOfDay(), 0);
    dt = dt.withField(DateTimeFieldType.dayOfMonth(), 1);
    return dt.toDate();
  }

  public static Date roundToYear(Date origin) {
    DateTime dt = new DateTime(origin);
    dt = dt.withField(DateTimeFieldType.millisOfSecond(), 0);
    dt = dt.withField(DateTimeFieldType.secondOfMinute(), 0);
    dt = dt.withField(DateTimeFieldType.minuteOfHour(), 0);
    dt = dt.withField(DateTimeFieldType.hourOfDay(), 0);
    dt = dt.withField(DateTimeFieldType.dayOfMonth(), 1);
    dt = dt.withField(DateTimeFieldType.monthOfYear(), 1);
    return dt.toDate();
  }

  /**
   * 以interval分钟为间隔,返回origin时间中的最大时间.
   *
   * @param origin   origin time
   * @param interval interval minutes
   * @return date
   */
  public static Date roundToMinuteOfInterval(Date origin, int interval) {
    if (interval > 60 || interval <= 0) {
      throw new RuntimeException("interval must be in (0,60)");
    }
    DateTime dt = new DateTime(origin);
    int minute = dt.getMinuteOfHour();
    int minus = minute % interval;
    dt = dt.plusMinutes(-minus);
    return roundToMinute(dt.toDate());
  }
}

